﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Passion3DProject.src.Cameras_Folder;
namespace Passion3DProject.src.Models_s_Classes
{
    class GameObject
    {
        #region Atributes
        private Vector3 position;
        /// <summary>
        /// Posição do Objeto
        /// </summary>
        public Vector3 Position
        {
            get
            {
                return this.position;
            }
            set
            {
                this.position = value;
            }
        }
        /// <summary>
        /// Content
        /// </summary>
        protected ContentManager Content {get; set;}

        private BoundingBox collisionBox;
        /// <summary>
        /// Esfera de Colisão do Objeto
        /// </summary>
        public BoundingBox CollisionBox { get { return this.collisionBox; } set { this.collisionBox = value; } }

        private BoundingBox futureCollisionBox;
        /// <summary>
        /// Esfera de Colisão do Objeto
        /// </summary>
        public BoundingBox FutureCollisionBox { get { return this.futureCollisionBox; } set { this.futureCollisionBox = value; } }

        private BoundingSphere collisionSphere;
        /// <summary>
        /// Esfera de Colisão do Objeto
        /// </summary>
        public BoundingSphere CollisionSphere { get { return this.collisionSphere; } set { this.collisionSphere = value; } }

        private Model objModel;
        /// <summary>
        /// Modelo do Objeto que será carregado
        /// </summary>
        public Model ObjModel { get { return this.objModel; } set { this.objModel = value; } }
        /// <summary>
        /// Construtor 1 - Sem parâmetro porque há objetos que não tem modelo
        /// </summary>
        
        private float rotationX;
        /// <summary>
        /// Rotação do Objeto
        /// </summary>
        public float RotationX { get { return this.rotationX; } set { this.rotationX = value; } }

        private float rotationY;
        /// <summary>
        /// Rotação do Objeto
        /// </summary>
        public float RotationY { get { return this.rotationY; } set { this.rotationY = value; } }

        private float rotationZ;
        /// <summary>
        /// Rotação do Objeto
        /// </summary>
        public float RotationZ { get { return this.rotationZ; } set { this.rotationZ = value; } }

        private float scaleX;
        /// <summary>
        /// Comprimento do Objeto
        /// </summary>
        public float ScaleX { get { return this.scaleX; } set { this.scaleX = value; } }
        
        private float scaleY;
        /// <summary>
        /// Comprimento do Objeto
        /// </summary>
        public float ScaleY { get { return this.scaleY; } set { this.scaleY = value; } }
        
        private float scaleZ;
        /// <summary>
        /// Comprimento do Objeto
        /// </summary>
        public float ScaleZ { get { return this.scaleZ; } set { this.scaleZ = value; } }
        #endregion

        #region Methods
        #region Construtores
        /// <summary>
        /// Construtor 1 - Sem modelo
        /// </summary>
        public GameObject()
        {
            Position = Vector3.Zero;
            ScaleX = ScaleY = ScaleZ = 1;

            CollisionBox = new BoundingBox();
            CollisionSphere = new BoundingSphere();
        }
        /// <summary>
        /// Contrutor 2 - Para objetos que tem modelo
        /// </summary>
        /// <param name="content">Content</param>
        /// <param name="modelType">Modelo do Objeto</param>
        
        public GameObject(ContentManager content,string modelType)
        {
            this.Content = content;
            ObjModel = content.Load<Model>(modelType);

            Position = Vector3.Zero;
            ScaleX = ScaleY = ScaleZ = 1;
        }
        #endregion

        /// <summary>
        /// Draw do Objeto
        /// </summary>
        /// <param name="camera">Câmera Corrente</param>
        #region Draw
        public void DrawModel(ThirdPersonCamera camera)
        {
            Matrix[] transforms = new Matrix[ObjModel.Bones.Count];
            ObjModel.CopyAbsoluteBoneTransformsTo(transforms);

            foreach (ModelMesh mesh in this.ObjModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = Matrix.CreateRotationX(RotationX) *
                                                Matrix.CreateRotationY(RotationY) *
                                                Matrix.CreateRotationZ(RotationZ) *
                                                Matrix.CreateScale(ScaleX, ScaleY, ScaleZ) *
                                                Matrix.CreateTranslation(Position.X, Position.Y, Position.Z) *
                                                transforms[mesh.ParentBone.Index];

                    effect.View = camera.viewCamMatrix;
                    effect.Projection = camera.ProjectionCamMatrix;

                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;
                }
                mesh.Draw();
            }
        }
        #endregion

        /// <summary>
        /// Método que Atualiza a Esfera de Colisão do Objeto
        /// </summary>
        /// <param name="objModel">Modelo do Objeto</param>
        /// <param name="worldmatrix">matriz de mundo do Objeto</param>
        /// <returns>Retorna a uma esfera de colisão com o tamanho certo do Objeto, segundo sua escala configurada</returns>
        #region Bouding box's Update
        public BoundingBox UpdateBoundingBox(Model objModel, Matrix worldObjmatrix)
        {
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            foreach (ModelMesh mesh in objModel.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    //Representa o número de bytes de um vértex para o próximo
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    //Representa o tamanho do BUFFER (array) de vértices
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;
                    //Um array de vertex com o tamanho certo (sizeof(float)) para caber o vertexBuffer
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    //Execução do Método que preenche o BUFFER de vértices com as informações de FLOAT que o GETDATA retorna.
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    //Percorre cada ponto, de zero até o tamanho do Buffer de Vértices em float, a fim de verificar cada ponto e executar a seguinte função:
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        //cria um vertor transformado com os tres pontos em relação ao mundo
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), worldObjmatrix);

                        //Calcula os ponto Mínimo e Máximo
                        min = Vector3.Min(min, transformedPosition);
                        max = Vector3.Max(max, transformedPosition);
                    }
                }
            }
            //Retorna ao tamanho certo do Objeto Transoformado em relação a sua posição no mundo
            return new BoundingBox(min, max);
        }
        #endregion

        /// <summary>
        /// Update Principal.
        /// Neste Update, nós atualizamos a posição do objeto em relação à 
        /// sua posição e estado no mundo para que, enfim, nossa caixa de colisão 
        /// possa ser atualizada e utilizada.
        /// </summary>
        /// <param name="gameTime"></param>
        #region Update
        public virtual void Update(GameTime gameTime)
        {
            CollisionBox = UpdateBoundingBox(ObjModel,UpdateWorldPosition(Position.X, Position.Y, Position.Z));
        }
        #endregion

        /// <summary>
        /// Atualização da colisão do Objeto em relação ao mundo
        /// segundo suas respectivas características como: Posição no mundo,
        /// Tamanho em função de sua escala e sua rotação, ou seja, 
        /// Como o objeto se encontra neste exato momento.
        /// </summary>
        /// <param name="PositionX">Posição X</param>
        /// <param name="PositionY">Posição Y</param>
        /// <param name="PositionZ">Posição Z</param>
        /// <returns></returns>
        #region Updating the World Position
        public Matrix UpdateWorldPosition(float PositionX, float PositionY, float PositionZ)
        {
            Matrix toReturn = Matrix.CreateRotationX(RotationX) *
                                                    Matrix.CreateRotationY(RotationY) *
                                                    Matrix.CreateRotationZ(RotationZ) *
                                                    Matrix.CreateScale(ScaleX, ScaleY, ScaleZ) *
                                                    Matrix.CreateTranslation(PositionX, PositionY, PositionZ);

            return toReturn;

        }
        #endregion

        #endregion
    }
}
